/*---------------------------------------------------------------------------*
 * Copyright (C) 2014 Ryan M. Murphy <ryan.m.murphy.77@gmail.com>
 *
 * File Name   : drv_chrg.h
 *
 * Project     : Watts-rock
 *               <http://code.google.com/p/watts-rock-motherboard/>
 *
 * Description : This driver is responsible for controlling the Watts-rock
 *               battery charge/discharge FETs, reading the voltage/current,
 *               monitoring the temperature, and charging the battery.
 *
 * References  : 1. AVR121: Enhancing ADC resolution by oversampling.
 *               2. INA170: High-Side, Bidirectional CURRENT SHUNT MONITOR
 * Last Update : Jan 4, 2014
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Include Files
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "arb_device.h"
#include "arb_semaphore.h"
#include "drv_chrg.h"
#include "utl_adc.h"
#include "utl_gpio.h"
#include "utl_timer.h"

/*---------------------------------------------------------------------------*
 * Private Defines
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * The ADC reference voltage...
 *---------------------------------------------------------------------------*/
#define CHRG_ADC_VREF                    (2.507f)
#define CHRG_DEFAULT_ADC_SIGNED_NUM_BITS (11) /*2^11 - signed 12 bit*/
#define CHRG_CM_ROS                      (10000.0f)
#define CHRG_CM_RL                       (4990.0f)
#define CHRG_CM_NOM_VOLTS                (CHRG_ADC_VREF*CHRG_CM_RL/CHRG_CM_ROS)

/*---------------------------------------------------------------------------*
 * The number of bits required to change the signed 2^11 ADC value into a
 * signed 2^15 ADC value.
 *---------------------------------------------------------------------------*/
#define CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS (5)
#define CHRG_OVERSAMPLING_SCALE_FACTOR       (1<<\
CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS) /*See [1] eq 3-1*/
#define CHRG_OVERSAMPLING_AVERAGE_AMOUNT     (CHRG_OVERSAMPLING_SCALE_FACTOR*\
CHRG_OVERSAMPLING_SCALE_FACTOR)       /*See [1] eq 3-2*/
#define CHRG_OVERSAMPLING_NUM_FRAMES         (1)
#define CHRG_DEFAULT_NUM_MEAS                (CHRG_OVERSAMPLING_NUM_FRAMES*\
CHRG_OVERSAMPLING_AVERAGE_AMOUNT)
#define CHRG_CHARGE_PWM_RATE                 (100000) /*Hz*/
#define CHRG_CHARGE_INITIAL_PWM_PERC         (.00f)     /*Percent*/
//#define CHRG_ADC_DEBUG

#ifdef CHRG_ADC_DEBUG
   #define CHRG_DEBUG_SIZE 700
   //int16_t as_vltAdcSamples[CHRG_DEBUG_SIZE];
   int16_t gs_vltSamplePtr = 0;
   int16_t as_currAdcSamples[CHRG_DEBUG_SIZE];
   int16_t gs_currSamplePtr = 0;
   //int16_t as_tempAdcSamples[CHRG_DEBUG_SIZE];
   int16_t gs_tempSamplePtr = 0;
#endif

/*---------------------------------------------------------------------------*
 * The TOP fixed-point value of the ADC after oversampling has been
 * performed.
 *---------------------------------------------------------------------------*/
#define CHRG_ADC_TOP_SIGNED (float)(\
(int32_t)1 << (CHRG_DEFAULT_ADC_SIGNED_NUM_BITS + CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS))

/*---------------------------------------------------------------------------*
 * Converts the ADC measurement from a fixed-point value into its floating
 * point representation.
 *---------------------------------------------------------------------------*/
#define CHRG_ADC_FXD_TO_FLOAT_SIGNED (CHRG_ADC_VREF/CHRG_ADC_TOP_SIGNED)

/*---------------------------------------------------------------------------*
 * Private Data Types
 *---------------------------------------------------------------------------*/
typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the volt meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calVltMtrBias;
   /*------------------------------------------------------------------------*
    * The true amount of scaling by the power meter voltage divider. This
    * value is determined during factory calibration by measuring the voltage
    * on both sides on the power meter voltage divider.
    *------------------------------------------------------------------------*/
   float f_calVltMtrRDivide;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated volt meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_voltageSum;

}t_voltMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the current meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calCurMtrBias;
   /*------------------------------------------------------------------------*
    * The measured value of the r-sense resistor nominally .047 ohms
    *------------------------------------------------------------------------*/
   float f_calRsense;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated current meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_currentSum;

}t_currMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Calibration factor that removes the DC bias on the temp meter
    * measurement.
    *------------------------------------------------------------------------*/
   float f_calTempMtrBias;
   /*------------------------------------------------------------------------*
    * The accumulated bias compensated temperature meter ADC measurement.
    *------------------------------------------------------------------------*/
   int32_t i_tempSum;

}t_tempMeter;

typedef struct
{

   /*------------------------------------------------------------------------*
    * Resources that can be shared amongst multiple users (either a global
    * buffer or IO device) need to be protected against race conditions. We
    * use this semaphore for just that purpose.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_mutex;
   /*------------------------------------------------------------------------*
    * This semaphore is used for waking up the user-space thread once a
    * measurement has finished.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_blockingSem;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery voltage.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_vltMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery current.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_currMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the battery temperature.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_tempMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the ADC used for monitoring the Sepic input voltage.
    *------------------------------------------------------------------------*/
   t_ADCCHANHNDL t_sepicVoltMtrHndl;
   /*------------------------------------------------------------------------*
    * Handle to the timer used for controlling the SEPIC batter charger's
    * MOSFET.
    *------------------------------------------------------------------------*/
   t_TIMERHNDL t_timerHndl;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters and battery voltage estimate.
    *------------------------------------------------------------------------*/
   t_voltMeter t_vltMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters for the Sepic input voltage
    * estimate.
    *------------------------------------------------------------------------*/
   t_voltMeter t_sepicVltMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters for battery current estimate.
    *------------------------------------------------------------------------*/
   t_currMeter t_currMtr;
   /*------------------------------------------------------------------------*
    * Contains the factor calibration parameters and battery temp estimate.
    *------------------------------------------------------------------------*/
   t_tempMeter t_tempMtr;
   /*------------------------------------------------------------------------*
    * Copy of the setup parameters entered at the time of initialization.
    *------------------------------------------------------------------------*/
   t_chrgSetup t_chrgSetup;
   /*------------------------------------------------------------------------*
    * If true, an AC signal has been input into charger.
    *------------------------------------------------------------------------*/
   uint32_t i_acChargeDet;
   /*------------------------------------------------------------------------*
    * If true, a DC signal has been input into charger.
    *------------------------------------------------------------------------*/
   uint32_t i_dcChargeDet;
   /*------------------------------------------------------------------------*
    * Current average count.
    *------------------------------------------------------------------------*/
   int32_t i_measCount;
   /*------------------------------------------------------------------------*
    * We are going to want to know how many 'handles' or users are attached to
    * this device driver.
    *------------------------------------------------------------------------*/
   uint8_t c_numUsers;

}t_chrgDev;

/*---------------------------------------------------------------------------*
 * Private Function Prototypes
 *---------------------------------------------------------------------------*/
static t_error chrgOpen( t_DEVHANDLE t_handle);

static int16_t chrgRead( t_DEVHANDLE t_handle,
                        int8_t *pc_buff,
                        uint16_t s_size);

static int16_t chrgWrite( t_DEVHANDLE t_handle,
                         int8_t *pc_buff,
                         uint16_t s_size);

static int32_t chrgIoctl( t_DEVHANDLE t_handle,
                         uint16_t s_command,
                         int32_t  i_arguments);

static t_error chrgClose( t_DEVHANDLE t_handle);

static void voltMeasComplete( int16_t s_sample);

static void currMeasComplete( int16_t s_sample);

static void sepicVoltMeasComplete( int16_t s_sample);

static void chrgCaptureNewMeas( void);

/*---------------------------------------------------------------------------*
 * Private Global Variables
 *---------------------------------------------------------------------------*/
t_deviceOperations gt_chrgDevOps =
{
    chrgOpen,
    chrgRead,
    chrgWrite,
    chrgIoctl,
    chrgClose

};

/*---------------------------------------------------------------------------*
 * This is the device's shared memory, all actions on this variable must be
 * mutually exclusive.
 *---------------------------------------------------------------------------*/
static t_chrgDev gt_chrgDev;

/*---------------------------------------------------------------------------*
 * Inline Function Definitions
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Private Function Definitions
 *---------------------------------------------------------------------------*/
static void voltMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_chrgDev.t_vltMtr.i_voltageSum += (int32_t)s_sample;

#ifdef CHRG_ADC_DEBUG
   //as_vltAdcSamples[gs_vltSamplePtr] = s_sample;
   gs_vltSamplePtr++;
   if( gs_vltSamplePtr == CHRG_DEBUG_SIZE)
      gs_vltSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_chrgDev.t_tempMtrHndl);

}/*End voltMeasComplete*/

static void tempMeasComplete( int16_t s_sample)
{

   t_adcError t_adcErr;

   gt_chrgDev.t_tempMtr.i_tempSum += (int32_t)s_sample;

#ifdef CHRG_ADC_DEBUG
   //as_tempAdcSamples[gs_tempSamplePtr] = s_sample;
   gs_tempSamplePtr++;
   if( gs_tempSamplePtr == CHRG_DEBUG_SIZE)
      gs_tempSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_chrgDev.t_currMtrHndl);

}/*End tempMeasComplete*/

static void currMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_chrgDev.t_currMtr.i_currentSum += (int32_t)s_sample;

#ifdef CHRG_ADC_DEBUG
   //as_currAdcSamples[gs_currSamplePtr] = s_sample;
   //gs_currSamplePtr++;
   //if( gs_currSamplePtr == CHRG_DEBUG_SIZE)
   //   gs_currSamplePtr = 0;
#endif

   t_adcErr = utl_startAdcConversion( gt_chrgDev.t_sepicVoltMtrHndl);

}/*End currMeasComplete*/

static void sepicVoltMeasComplete( int16_t s_sample)
{
   t_adcError t_adcErr;

   gt_chrgDev.t_sepicVltMtr.i_voltageSum += (int32_t)s_sample;

   /*------------------------------------------------------------------------*
    * Has an AC signal been connected to the charger?
    *------------------------------------------------------------------------*/
   gt_chrgDev.i_acChargeDet +=
   (uint32_t)utl_isGpioHigh( gt_chrgDev.t_chrgSetup.c_chargeDetPort,
                             gt_chrgDev.t_chrgSetup.c_acChargeDetPin);

   /*------------------------------------------------------------------------*
    * Has a DC signal been connected to the charger?
    *------------------------------------------------------------------------*/
   gt_chrgDev.i_dcChargeDet +=
   (uint32_t)utl_isGpioHigh( gt_chrgDev.t_chrgSetup.c_chargeDetPort,
                             gt_chrgDev.t_chrgSetup.c_dcChargeDetPin);

   gt_chrgDev.i_measCount++;
   if( gt_chrgDev.i_measCount == CHRG_DEFAULT_NUM_MEAS)
   {

      gt_chrgDev.i_measCount = 0;
      arb_signal( gt_chrgDev.t_blockingSem);
   }
   else
   {
      t_adcErr = utl_startAdcConversion( gt_chrgDev.t_vltMtrHndl);
   }

}/*End sepicVoltMeasComplete*/

static t_error chrgOpen( t_DEVHANDLE t_handle)
{
   t_error t_err = ARB_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Keep track of the number of user-space applications using the driver.
    *------------------------------------------------------------------------*/
   gt_chrgDev.c_numUsers++;

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_chrgDev.t_mutex);

   return t_err;

}/*End chrgOpen*/

static int16_t chrgRead( t_DEVHANDLE t_handle,
                        int8_t *pc_buff,
                        uint16_t s_size)
{
   int16_t s_bufferLevel = 0;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Read the battery voltage, current, temperature, and AC/DC charger
    * detected status pins.
    *------------------------------------------------------------------------*/

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_chrgDev.t_mutex);

   return s_bufferLevel;

}/*End chrgRead*/

static int16_t chrgWrite( t_DEVHANDLE t_handle,
                         int8_t *pc_buff,
                         uint16_t s_size)
{

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_chrgDev.t_mutex);

   return s_size;

}/*End chrgWrite*/

static void chrgCaptureNewMeas( void)
{
   t_adcError t_adcErr;

   /*------------------------------------------------------------------------*
    * Reset last measurements..
    *------------------------------------------------------------------------*/
   gt_chrgDev.t_vltMtr.i_voltageSum       = 0;
   gt_chrgDev.t_currMtr.i_currentSum      = 0;
   gt_chrgDev.t_tempMtr.i_tempSum         = 0;
   gt_chrgDev.t_sepicVltMtr.i_voltageSum  = 0;
   gt_chrgDev.i_acChargeDet               = 0;
   gt_chrgDev.i_dcChargeDet               = 0;

   t_adcErr = utl_startAdcConversion( gt_chrgDev.t_vltMtrHndl);

   /*------------------------------------------------------------------------*
    * Wait for the measurements to finish...
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_blockingSem,
             0);

}/*End chrgCaptureNewMeas*/
float f_vSum = 0.0f;
static int32_t chrgIoctl( t_DEVHANDLE t_handle,
                         uint16_t s_command,
                         int32_t i_arguments)
{
   int32_t i_return = (int32_t)CHRG_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_mutex,
             0);

   switch( (t_chrgCmd)s_command)
   {
      case CHRG_DC_CHRG_CNTRL:

         if( (i_arguments > CHRG_DEVICE_ON) || (i_arguments < CHRG_DEVICE_OFF))
            i_return = CHRG_INVALID_ARG;
         else
         {
            if( i_arguments == CHRG_DEVICE_ON)
            {
               utl_gpioOn( gt_chrgDev.t_chrgSetup.c_dcChrgSourcePort,
                           gt_chrgDev.t_chrgSetup.c_dcChrgSourcePin);
            }
            else
            {
               utl_gpioOff( gt_chrgDev.t_chrgSetup.c_dcChrgSourcePort,
                            gt_chrgDev.t_chrgSetup.c_dcChrgSourcePin);
            }
         }
      break;/*End case CHRG_DC_CHRG_CNTRL:*/

      case CHRG_AC_CHRG_CNTRL:

         if( (i_arguments > CHRG_DEVICE_ON) || (i_arguments < CHRG_DEVICE_OFF))
            i_return = CHRG_INVALID_ARG;
         else
         {
            if( i_arguments == CHRG_DEVICE_ON)
            {
               utl_gpioOn( gt_chrgDev.t_chrgSetup.c_acChrgSourcePort,
                           gt_chrgDev.t_chrgSetup.c_acChrgSourcePin);
            }
            else
            {
               utl_gpioOff( gt_chrgDev.t_chrgSetup.c_acChrgSourcePort,
                            gt_chrgDev.t_chrgSetup.c_acChrgSourcePin);
            }
         }
      break;/*End case CHRG_AC_CHRG_CNTRL:*/

      case CHRG_FAN_CNTRL:

         if( (i_arguments > CHRG_DEVICE_ON) || (i_arguments < CHRG_DEVICE_OFF))
            i_return = CHRG_INVALID_ARG;
         else
         {
            if( i_arguments == CHRG_DEVICE_ON)
            {
               utl_gpioOn( gt_chrgDev.t_chrgSetup.c_fanPort,
                           gt_chrgDev.t_chrgSetup.c_fanPin);
            }
            else
            {
               utl_gpioOff( gt_chrgDev.t_chrgSetup.c_fanPort,
                            gt_chrgDev.t_chrgSetup.c_fanPin);
            }

         }

      break;/*End case CHRG_FAN_CNTRL:*/

      case CHRG_PWR_CNTRL:

         if( (i_arguments > CHRG_DEVICE_ON) || (i_arguments < CHRG_DEVICE_OFF))
            i_return = CHRG_INVALID_ARG;
         else
         {
            if( i_arguments == CHRG_DEVICE_ON)
            {
               utl_gpioOn( gt_chrgDev.t_chrgSetup.c_shutdownPort,
                           gt_chrgDev.t_chrgSetup.c_shutdownPin);
            }
            else
            {
               utl_gpioOff( gt_chrgDev.t_chrgSetup.c_shutdownPort,
                            gt_chrgDev.t_chrgSetup.c_shutdownPin);
            }

         }

      break;/*End case CHRG_PWR_CNTRL:*/

      case CHRG_CHARGER_CNTRL:
         /*------------------------------------------------------------------*
          * Turn on/off the PWM signal that drives the N-channel SEPIC
          * charge controller MOSFET.
          *------------------------------------------------------------------*/
         if( (i_arguments > CHRG_DEVICE_ON) || (i_arguments < CHRG_DEVICE_OFF))
            i_return = CHRG_INVALID_ARG;
         else
         {
            if( i_arguments == CHRG_DEVICE_ON)
            {
               uint16_t s_compareTicks = 0;

               /*------------------------------------------------------------*
                * For now, set the compare value low so that the sepic
                * converter outputs a low voltage...
                *------------------------------------------------------------*/
               s_compareTicks = (uint16_t)(CHRG_CHARGE_INITIAL_PWM_PERC*
               utl_getPeriodValue(gt_chrgDev.t_timerHndl));

               utl_setCompareValue( gt_chrgDev.t_timerHndl,
                                    gt_chrgDev.t_chrgSetup.t_chargeFetCompType,
                                    s_compareTicks);

               utl_enableCompareChannel( gt_chrgDev.t_timerHndl,
                                         gt_chrgDev.t_chrgSetup.
                                         t_chargeFetCompType,
                                         true);

               utl_startTimer( gt_chrgDev.t_timerHndl);

            }
            else
            {
               utl_disableCompareChannel( gt_chrgDev.t_timerHndl,
                                          gt_chrgDev.t_chrgSetup.
                                          t_chargeFetCompType);

               utl_stopTimer( gt_chrgDev.t_timerHndl);

            }

         }

      break;/*End case CHRG_CHARGER_CNTRL:*/

      case CHRG_CHARGER_SET_DUTY_CYCLE:
      {
         float *pf_errPerc = (float *)((uint16_t)i_arguments);

         if( *pf_errPerc < 0.0f)
            *pf_errPerc = 0.0f;
         else if( *pf_errPerc > .90f)
            *pf_errPerc = 0.90f;

         /*------------------------------------------------------------------*
          * Adjust the charge FET PWM duty cycle...
          *------------------------------------------------------------------*/
         utl_setCompareValue( gt_chrgDev.t_timerHndl,
                              gt_chrgDev.t_chrgSetup.t_chargeFetCompType,
                              (uint16_t)((*pf_errPerc)*
                              utl_getPeriodValue(gt_chrgDev.t_timerHndl)));

      }
      break; /*End case CHRG_CHARGER_SET_DUTY_CYCLE:*/

      case CHRG_STORE_CAL:
      {
         t_chrgCal *pt_chrgCal = (t_chrgCal *)((uint16_t)i_arguments);

         gt_chrgDev.t_vltMtr.f_calVltMtrBias = pt_chrgCal->f_calVltMtrBias;
         gt_chrgDev.t_vltMtr.f_calVltMtrRDivide = pt_chrgCal->f_calVltMtrRDivide;

         gt_chrgDev.t_currMtr.f_calCurMtrBias = pt_chrgCal->f_calCurMtrBias;
         gt_chrgDev.t_currMtr.f_calRsense = pt_chrgCal->f_calRsense;

         gt_chrgDev.t_tempMtr.f_calTempMtrBias = pt_chrgCal->f_calTempMtrBias;

         gt_chrgDev.t_sepicVltMtr.f_calVltMtrBias = pt_chrgCal->f_calSepicVltMtrBias;
         gt_chrgDev.t_sepicVltMtr.f_calVltMtrRDivide = pt_chrgCal->f_calSepicVltMtrRDivide;

      }
      break;/*End case CHRG_STORE_CAL:*/

      case CHRG_GET_MEAS:
      {
         t_chrgMeas *pt_chrgMeas;
         float f_volts;
         float f_current;
         float f_temp;
         float f_sepicVolts;

         chrgCaptureNewMeas();

         /*------------------------------------------------------------------*
          * Shift the accumulated values by
          * 2^CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS in order to obtain a 2^15
          * bit number.
          *------------------------------------------------------------------*/
         gt_chrgDev.t_vltMtr.i_voltageSum >>=
         CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_chrgDev.t_currMtr.i_currentSum >>=
         CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_chrgDev.t_tempMtr.i_tempSum >>=
         CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS;
         gt_chrgDev.t_sepicVltMtr.i_voltageSum >>=
         CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS;

         gt_chrgDev.i_acChargeDet = gt_chrgDev.i_acChargeDet << 3;
         gt_chrgDev.i_dcChargeDet = gt_chrgDev.i_dcChargeDet << 3;
         gt_chrgDev.i_acChargeDet = gt_chrgDev.i_acChargeDet >>
         (CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS*2);
         gt_chrgDev.i_dcChargeDet = gt_chrgDev.i_dcChargeDet >>
         (CHRG_OVERSAMPLING_DESIRED_EXTRA_BITS*2);

#ifdef CHRG_ADC_DEBUG
   as_currAdcSamples[gs_currSamplePtr] = gt_chrgDev.t_currMtr.i_currentSum;
   gs_currSamplePtr++;
   if( gs_currSamplePtr == CHRG_DEBUG_SIZE)
      gs_currSamplePtr = 0;
#endif

         /*------------------------------------------------------------------*
          * Divide the accumulated values by the number of oversampling fames
          * and convert from a fixed-point ADC value to a floating point
          * voltage, current, and temperature respectively.
          *------------------------------------------------------------------*/
         f_volts = ((float)gt_chrgDev.t_vltMtr.i_voltageSum /
         CHRG_OVERSAMPLING_NUM_FRAMES)*CHRG_ADC_FXD_TO_FLOAT_SIGNED;
         f_volts = f_volts - gt_chrgDev.t_vltMtr.f_calVltMtrBias;
         f_volts = f_volts*gt_chrgDev.t_vltMtr.f_calVltMtrRDivide;

         f_sepicVolts = ((float)gt_chrgDev.t_sepicVltMtr.i_voltageSum /
         CHRG_OVERSAMPLING_NUM_FRAMES)*CHRG_ADC_FXD_TO_FLOAT_SIGNED;
         f_sepicVolts = (f_sepicVolts - gt_chrgDev.t_sepicVltMtr.f_calVltMtrBias);
         f_sepicVolts = f_sepicVolts*gt_chrgDev.t_sepicVltMtr.f_calVltMtrRDivide;

         /*------------------------------------------------------------------*
          * Convert from fixed point to floating point and remove bias...
          *------------------------------------------------------------------*/
         f_current = ((float)gt_chrgDev.t_currMtr.i_currentSum /
         CHRG_OVERSAMPLING_NUM_FRAMES)*CHRG_ADC_FXD_TO_FLOAT_SIGNED;

         f_current = (f_current - gt_chrgDev.t_currMtr.f_calCurMtrBias);

         /*------------------------------------------------------------------*
          * Calculate current through sense resistor using equation 4 of [2].
          *
          *------------------------------------------------------------------*/
         f_current = f_current - CHRG_CM_NOM_VOLTS;
         f_current = f_current*1000.0f/(CHRG_CM_RL*gt_chrgDev.t_currMtr.f_calRsense);

         f_temp = ((float)gt_chrgDev.t_tempMtr.i_tempSum /
         CHRG_OVERSAMPLING_NUM_FRAMES)*CHRG_ADC_FXD_TO_FLOAT_SIGNED;
         f_temp = (f_temp - gt_chrgDev.t_tempMtr.f_calTempMtrBias);

         /*------------------------------------------------------------------*
          * Calculate the temperature according to section 4.0 of the
          * MCP9700T temperature sensor data sheet, reference
          * http://ww1.microchip.com/downloads/en/DeviceDoc/21942e.pdf for
          * more information.
          *------------------------------------------------------------------*/
         f_temp = (f_temp - .4f) / .0195f;//(f_temp - .5f) / .01f;

         /*------------------------------------------------------------------*
          * Grab pointer to the user-space token variable...
          *------------------------------------------------------------------*/
         pt_chrgMeas = (t_chrgMeas *)((uint16_t)i_arguments);

         /*------------------------------------------------------------------*
          * Populate new measurements...
          *------------------------------------------------------------------*/
         pt_chrgMeas->f_volts        = f_volts;
         pt_chrgMeas->f_sepicVolts   = f_sepicVolts;
         pt_chrgMeas->f_current      = f_current;
         pt_chrgMeas->f_temp         = f_temp;

         if( gt_chrgDev.i_acChargeDet > 6)
            pt_chrgMeas->b_acChargeDet = true;
         else
            pt_chrgMeas->b_acChargeDet = false;

         if( gt_chrgDev.i_dcChargeDet > 6)
            pt_chrgMeas->b_dcChargeDet = true;
         else
            pt_chrgMeas->b_dcChargeDet = false;
      }
      break;/*End case CHRG_GET_MEAS:*/

      default:

         i_return = (int32_t)CHRG_INVALID_CMD;

      break;

   }/*End switch( (t_chrgCmd)s_command)*/

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_chrgDev.t_mutex);

   return i_return;

}/*End chrgIoctl*/

/*---------------------------------------------------------------------------*
 * Remove this particular file attached to this device
 *---------------------------------------------------------------------------*/
static t_error chrgClose( t_DEVHANDLE t_handle)
{
   t_error t_err = ARB_PASSED;

   /*------------------------------------------------------------------------*
    * We are going to access global memory or a register, so perform lock
    *------------------------------------------------------------------------*/
   arb_wait( gt_chrgDev.t_mutex,
             0);

   /*------------------------------------------------------------------------*
    * Keep track of the number of user-space applications using the driver.
    *------------------------------------------------------------------------*/
   gt_chrgDev.c_numUsers--;

   /*------------------------------------------------------------------------*
    * Release the lock
    *------------------------------------------------------------------------*/
   arb_signal( gt_chrgDev.t_mutex);

   return t_err;

}/*End chrgClose*/

t_error drv_chrgInit( t_chrgSetup t_setup)
{
   t_error t_err = ARB_PASSED;
   t_adcModConf t_adcMod;
   t_adcChanConf t_adcChan;
   t_gpioConf t_gConf;
   t_timerConfig t_tConf;

   /*------------------------------------------------------------------------*
    * Clear the CHRG object..
    *------------------------------------------------------------------------*/
   memset( (void *)&gt_chrgDev, 0, sizeof( gt_chrgDev));

   /*------------------------------------------------------------------------*
    * Make sure the kernel is aware that a new device has been loaded.
    *------------------------------------------------------------------------*/
   t_err = arb_registerDevice( "chrgDevice0",
                               arb_createDevId( t_setup.c_majorNum, 0),
                               &gt_chrgDevOps);

   if( t_err < 0)
   {
      goto failed1;
   }

   /*------------------------------------------------------------------------*
    * Request a semaphore from the kernel. Since the signal port is a shared
    * resource we need to have all actions on it be mutually exclusive.
    *------------------------------------------------------------------------*/
   gt_chrgDev.t_mutex = arb_semaphoreCreate( MUTEX);

   if( gt_chrgDev.t_mutex < 0)
   {
      t_err = (t_error)gt_chrgDev.t_mutex;
      goto failed2;

   }/*End if( gt_chrgDev.t_mutex < 0)*/

   /*------------------------------------------------------------------------*
    * Request a semaphore from the kernel. We will use this semaphore for
    * signaling the user-space program when a voltage and current measurement
    * has finished.
    *------------------------------------------------------------------------*/
   gt_chrgDev.t_blockingSem = arb_semaphoreCreate( COUNTING);

   if( gt_chrgDev.t_blockingSem < 0)
   {
      t_err = (t_error)gt_chrgDev.t_blockingSem;
      goto failed3;

   }/*End if( gt_chrgDev.t_blockingSem < 0)*/

   /*------------------------------------------------------------------------*
    * Configure the battery volt meter ADC module.
    *------------------------------------------------------------------------*/
   t_adcMod.t_mode  = SIGNED;
   t_adcMod.t_res   = RES_12BIT;
   t_adcMod.t_ref   = EXTERNAL_PORTA;
   t_adcMod.t_clock = PERIPH_CLOCK_OVER_128;

   if( utl_configureAdcModule( t_setup.t_battVoltAdc, t_adcMod) < 0)
   {
      goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Configure the battery current meter ADC module.
    *------------------------------------------------------------------------*/
   t_adcMod.t_mode  = SIGNED;
   t_adcMod.t_res   = RES_12BIT;
   t_adcMod.t_ref   = EXTERNAL_PORTB;
   t_adcMod.t_clock = PERIPH_CLOCK_OVER_128;

   if( utl_configureAdcModule( t_setup.t_battCurrAdc, t_adcMod) < 0)
   {
     goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the battery voltage measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_battVoltPosPin;
   t_adcChan.c_negPin    = t_setup.c_battVoltNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &voltMeasComplete;

   gt_chrgDev.t_vltMtrHndl = utl_requestAdcChannel( t_setup.t_battVoltAdc,
                                                   t_adcChan);

   if( gt_chrgDev.t_vltMtrHndl < 0)
   {
      goto failed4;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the current measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_battCurrPosPin;
   t_adcChan.c_negPin    = t_setup.c_battCurrNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &currMeasComplete;

   gt_chrgDev.t_currMtrHndl = utl_requestAdcChannel( t_setup.t_battCurrAdc,
                                                    t_adcChan);

   if( gt_chrgDev.t_currMtrHndl < 0)
   {
      goto failed5;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the CHRG module temperature measuring task.
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_tempPosPin;
   t_adcChan.c_negPin    = t_setup.c_tempNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &tempMeasComplete;

   gt_chrgDev.t_tempMtrHndl = utl_requestAdcChannel( t_setup.t_tempAdc,
                                                    t_adcChan);

   if( gt_chrgDev.t_tempMtrHndl < 0)
   {
      goto failed6;
   }

   /*------------------------------------------------------------------------*
    * Configure the GPIO pins for detecting when an AC signal has been
    * connected to the charger.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask  = t_setup.c_acChargeDetPin;
   t_gConf.c_outputMask = 0;
   t_gConf.t_inConf     = PULLDOWN;
   t_gConf.t_outConf    = TOTEM;

   if( utl_configureGpioPort( t_setup.c_chargeDetPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the GPIO pins for detecting when a DC signal has been
    * connected to the charger.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask  = t_setup.c_dcChargeDetPin;
   t_gConf.c_outputMask = 0;
   t_gConf.t_inConf     = PULLDOWN;
   t_gConf.t_outConf    = TOTEM;

   if( utl_configureGpioPort( t_setup.c_chargeDetPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls powering on and off the motherboard.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_shutdownPin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_shutdownPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls powering on and off the fan.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_fanPin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_fanPort, t_gConf) < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Grab a handle to the charge FET timer.
    *------------------------------------------------------------------------*/
   gt_chrgDev.t_timerHndl = utl_requestTimer( t_setup.t_chargeFetTimer);
   if( gt_chrgDev.t_timerHndl < 0)
   {
      goto failed7;
   }

   /*------------------------------------------------------------------------*
    * Configure the charge FET timer for the required period.
    *------------------------------------------------------------------------*/
   t_tConf.t_mode   = SINGLE_SLOPE; /*PWM mode*/
   t_tConf.t_dir    = DIRECTION_UP;
   t_tConf.f_period = 1.0f/CHRG_CHARGE_PWM_RATE;

   if( utl_configureTimer( gt_chrgDev.t_timerHndl,
                           t_tConf) < 0)
   {
      goto failed8;
   }

   /*------------------------------------------------------------------------*
    * Request one of the virtual ADC ports on the given ADC module to perform
    * the task of measuring the Sepic converter input voltage
    *------------------------------------------------------------------------*/
   t_adcChan.c_posPin    = t_setup.c_sepicVoltPosPin;
   t_adcChan.c_negPin    = t_setup.c_sepicVoltNegPin;
   t_adcChan.t_inMode    = SINGLE_ENDED_EXT;
   t_adcChan.b_enableInt = true;
   t_adcChan.pf_funPtr   = &sepicVoltMeasComplete;

   gt_chrgDev.t_sepicVoltMtrHndl = utl_requestAdcChannel( t_setup.t_sepicVoltAdc,
                                                         t_adcChan);

   if( gt_chrgDev.t_sepicVoltMtrHndl < 0)
   {
      goto failed8;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls turning on/off the Sepic converter's
    * AC charge source.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_acChrgSourcePin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_acChrgSourcePort, t_gConf) < 0)
   {
      goto failed9;
   }

   /*------------------------------------------------------------------------*
    * Configure the pin that controls turning on/off the Sepic converter's
    * DC charge source.
    *------------------------------------------------------------------------*/
   t_gConf.c_inputMask    = 0;
   t_gConf.c_outputMask   = t_setup.c_dcChrgSourcePin;
   t_gConf.b_setOutputLow = true;
   t_gConf.t_outConf      = PULLDOWN;

   if( utl_configureGpioPort( t_setup.c_dcChrgSourcePort, t_gConf) < 0)
   {
      goto failed9;
   }

   gt_chrgDev.t_chrgSetup = t_setup;
   gt_chrgDev.c_numUsers = 0;

   return ARB_PASSED;

failed9:

   utl_releaseAdcChannel( gt_chrgDev.t_sepicVoltMtrHndl);

failed8:

   utl_releaseTimer( gt_chrgDev.t_timerHndl);

failed7:

   utl_releaseAdcChannel( gt_chrgDev.t_tempMtrHndl);

failed6:

   utl_releaseAdcChannel( gt_chrgDev.t_currMtrHndl);

failed5:

   utl_releaseAdcChannel( gt_chrgDev.t_vltMtrHndl);

failed4:

   arb_semaphoreDestroy( gt_chrgDev.t_blockingSem);

failed3:

   arb_semaphoreDestroy( gt_chrgDev.t_mutex);

failed2:

   arb_destroyDevice( "chrgDevice0");

failed1:

   return t_err;

}/*End drv_chrgInit*/

/*---------------------------------------------------------------------------*
 * Remove the device from the system
 *---------------------------------------------------------------------------*/
void drv_chrgExit( void)
{

   if( gt_chrgDev.t_mutex != 0) /*If created... destroy*/
   {
      utl_releaseAdcChannel( gt_chrgDev.t_sepicVoltMtrHndl);
      utl_releaseTimer( gt_chrgDev.t_timerHndl);
      utl_releaseAdcChannel( gt_chrgDev.t_tempMtrHndl);
      utl_releaseAdcChannel( gt_chrgDev.t_vltMtrHndl);
      utl_releaseAdcChannel( gt_chrgDev.t_currMtrHndl);
      arb_semaphoreDestroy( gt_chrgDev.t_blockingSem);
      arb_semaphoreDestroy( gt_chrgDev.t_mutex);
      arb_destroyDevice( "chrgDevice0");

      memset( (void *)&gt_chrgDev, 0, sizeof( gt_chrgDev));

   }/*End if( gt_chrgDev.t_mutex != 0)*/

}/*End drv_chrgExit*/
